package com.gjy.imiate.jdk.concurrent;

import java.io.Serializable;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author gjy
 * @version 1.0
 * @since 2025-08-28 14:51:06
 */
public class ImitateCopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, Serializable {

    private static final long serialVersionUID = 809161903033088854L;

    private transient final ReentrantLock lock = new ReentrantLock();
    private transient volatile Object[] array;

    public Object[] getArray() {
        return array;
    }

    public void setArray(Object[] array) {
        this.array = array;
    }

    public ImitateCopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

    public ImitateCopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] elements;
        if (c.getClass() == ImitateCopyOnWriteArrayList.class) {
            elements = ((ImitateCopyOnWriteArrayList<? extends E>) c).getArray();
        } else {
            elements = c.toArray();
            if (c.getClass() != ArrayList.class) {
                elements = Arrays.copyOf(elements, elements.length, Object[].class);
            }
        }
        setArray(elements);
    }

    public ImitateCopyOnWriteArrayList(E[] toCopyIn) {
        setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
    }

    @Override
    public int size() {
        return getArray().length;
    }

    @Override
    public boolean isEmpty() {
        return size() == 0;
    }

    private static boolean eq(Object o1, Object o2) {
        return o1 == null ? o2 == null : o1.equals(o2);
    }

    private static int indexOf(Object o, Object[] elements, int index, int fence) {
        if (o == null) {
            for (int i = index; i < fence; i++) {
                if (elements[i] == null) {
                    return i;
                }
            }
        } else {
            for (int i = 0; i < fence; i++) {
                if (o.equals(elements[i])) {
                    return i;
                }
            }
        }
        return -1;
    }

    private static int lastIndexOf(Object o, Object[] elements, int index) {
        if (o == null) {
            for (int i = index; i >= 0; i--) {
                if (elements[i] == null)
                    return i;
            }
        } else {
            for (int i = index; i >= 0; i--) {
                if (o.equals(elements[i]))
                    return i;
            }
        }
        return -1;
    }

    @Override
    public boolean contains(Object o) {
        Object[] elements = getArray();
        return indexOf(o, elements, 0, elements.length) >= 0;
    }

    @Override
    public Iterator<E> iterator() {
        return null;
    }

    @Override
    public Object[] toArray() {
        Object[] elements = getArray();
        return Arrays.copyOf(elements, elements.length);
    }

    @Override
    public <T> T[] toArray(T[] a) {
        Object[] elements = getArray();
        int len = elements.length;
        if (a.length < len) {
            return (T[]) Arrays.copyOf(elements, len, a.getClass());
        } else {
            System.arraycopy(elements, 0, a, 0, len);
            if (a.length > len) {
                a[len] = null;
            }
            return a;
        }
    }

    @Override
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        try {
            Object[] elements = getArray();
            int length = elements.length;
            Object[] newElements = Arrays.copyOf(elements, length + 1);
            newElements[length] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

    public boolean remove(Object o, Object[] snapshot, int index) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] current = getArray();
            int length = current.length;
            if (snapshot != current) {
                findIndex:
                {
                    int prefix = Math.min(index, length);
                    for (int i = 0; i < prefix; i++) {
                        if (current[i] != snapshot[i] && eq(o, current[i])) {
                            index = i;
                            break findIndex;
                        }
                    }
                    if (index >= length) {
                        return false;
                    }
                    if (current[index] == o) {
                        break findIndex;
                    }
                    index = indexOf(o, current, index, length);
                    if (index < 0) {
                        return false;
                    }
                }
                Object[] newElements = new Object[length - 1];
                System.arraycopy(current, 0, newElements, 0, index);
                System.arraycopy(current, index + 1, newElements, index, length - index - 1);
                setArray(newElements);
            }
            return true;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public boolean remove(Object o) {
        Object[] elements = getArray();
        int index = indexOf(o, elements, 0, elements.length);
        return index >= 0 && remove(o, elements, index);
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        return false;
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        return false;
    }

    @Override
    public boolean addAll(int index, Collection<? extends E> c) {
        return false;
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        return false;
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        return false;
    }

    @Override
    public void clear() {

    }

    private E get(Object[] a, int index) {
        return (E) a[index];
    }

    @Override
    public E get(int index) {
        return get(getArray(), index);
    }

    @Override
    public E set(int index, E element) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            E oldVlaue = get(elements, index);
            if (oldVlaue != element) {
                int length = elements.length;
                Object[] newElements = Arrays.copyOf(elements, length);
                newElements[index] = element;
                setArray(newElements);
            } else {
                setArray(elements);
            }
            return oldVlaue;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void add(int index, E element) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            if (index > len || index < 0)
                throw new IndexOutOfBoundsException();
            Object[] newElements;
            int numMoved = len - index;
            if (numMoved == 0) {
                newElements = Arrays.copyOf(elements, len + 1);
            } else {
                newElements = new Object[len + 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index, newElements, index + 1, numMoved);
            }
            newElements[index] = element;
            setArray(newElements);
        } finally {
            lock.unlock();
        }
    }

    @Override
    public E remove(int index) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            E oldValue = get(elements, index);
            int numMoved = len - index - 1;
            if (numMoved == 0) {
                setArray(Arrays.copyOf(elements, len - 1));
            } else {
                Object[] newElements = new Object[len - 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index + 1, newElements, index, numMoved);

                setArray(newElements);
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public int indexOf(Object o) {
        Object[] elements = getArray();
        return indexOf(o, elements, 0, elements.length);
    }

    public int indexOf(E o, int index) {
        Object[] elements = getArray();
        return indexOf(o, elements, index, elements.length);
    }

    @Override
    public int lastIndexOf(Object o) {
        Object[] elements = getArray();
        return lastIndexOf(o, elements, elements.length - 1);
    }

    public int lastIndexOf(E o, int index) {
        Object[] elements = getArray();
        return lastIndexOf(o, elements, index);
    }

    @Override
    public ListIterator<E> listIterator() {
        return null;
    }

    @Override
    public ListIterator<E> listIterator(int index) {
        return null;
    }

    @Override
    public List<E> subList(int fromIndex, int toIndex) {
        return Collections.emptyList();
    }

    private static final sun.misc.Unsafe UNSAFE;
    private static final long lockOffset;

    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> k = ImitateCopyOnWriteArrayList.class;
            lockOffset = UNSAFE.objectFieldOffset(k.getDeclaredField("lock"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }

    // Support for resetting lock while deserializing
    private void resetLock() {
        UNSAFE.putObjectVolatile(this, lockOffset, new ReentrantLock());
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        try {
            ImitateCopyOnWriteArrayList<E> clone = (ImitateCopyOnWriteArrayList<E>) super.clone();
            clone.resetLock();
            return clone;
        } catch (Exception e) {
            throw new InternalError();
        }
    }
}
